package com.blm.service.impl;

import com.blm.common.constants.CacheConstant;
import com.blm.common.constants.ExceptionConstant;
import com.blm.dto.PasswordUpdateDTO;
import com.blm.dto.RegisterDTO;
import com.blm.dto.UserProfileUpdateDTO;
import com.blm.entity.User;
import com.blm.exception.CommonException;
import com.blm.repository.UserRepository;
import com.blm.service.UserService;
import com.blm.vo.UserVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CachePut;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;

import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired @Lazy
    private UserService self;

    @Override
    @Transactional
    @CachePut(value = CacheConstant.USER_VO, key = "#result.id")
    public UserVO register(RegisterDTO registerDTO) {
        // Check if username or phone already exists
        if (userRepository.existsByUsername(registerDTO.getUsername())) {
            throw new CommonException(ExceptionConstant.USER_ALREADY_EXISTS);
        }
        if (userRepository.existsByPhone(registerDTO.getPhone())) {
            throw new CommonException(ExceptionConstant.USER_PHONE_ALREADY_REGISTERED);
        }

        User user = new User();
        BeanUtils.copyProperties(registerDTO, user);
        user.setPassword(passwordEncoder.encode(registerDTO.getPassword()));
        user.setRole(registerDTO.getRole() == null ? User.UserRole.USER.toString() : registerDTO.getRole().toString());
        user.setStatus(User.ACTIVE); // Default status active
        user.setCreatedAt(LocalDateTime.now());
        user.setUpdatedAt(LocalDateTime.now());

        userRepository.save(user);

        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }

    @Override
    @Cacheable(value = CacheConstant.USER_DETAILS, key = "#username")
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new CommonException(ExceptionConstant.USER_NOT_FOUND));

        // 根据 user.getRole() 构建权限列表
        List<GrantedAuthority> authorities = Arrays.stream(user.getRole().split(","))
                .map(String::trim)
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList());

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                authorities
        );
    }

    @Override
    public UserDetails getUserDetails(User user) throws UsernameNotFoundException {
        // 根据 user.getRole() 构建权限列表
        List<GrantedAuthority> authorities = Arrays.stream(user.getRole().split(","))
                .map(String::trim)
                .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                .collect(Collectors.toList());

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                authorities
        );
    }

    @Override
    @Cacheable(value = CacheConstant.USER_VO, key = "#userId")
    public UserVO getUserProfile(Long userId) {
        User user = self.findUserById(userId);
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = {CacheConstant.USER_VO, CacheConstant.USER}, key = "#userId"),
        @CacheEvict(value = CacheConstant.USER_DETAILS, key = "#result.username")
    })
    public UserVO updateUserProfile(Long userId, UserProfileUpdateDTO profileUpdateDTO) {
        User user = self.findUserById(userId);

        // Check if new username conflicts with others
        if (StringUtils.hasText(profileUpdateDTO.getUsername()) && !user.getUsername().equals(profileUpdateDTO.getUsername())) {
             if (userRepository.existsByUsername(profileUpdateDTO.getUsername())) {
                 throw new CommonException(ExceptionConstant.USER_ALREADY_EXISTS);
             }
             user.setUsername(profileUpdateDTO.getUsername());
        }

        if (StringUtils.hasText(profileUpdateDTO.getEmail())) {
            user.setEmail(profileUpdateDTO.getEmail());
        }
        if (StringUtils.hasText(profileUpdateDTO.getAvatar())) {
            user.setAvatar(profileUpdateDTO.getAvatar());
        }
        user.setUpdatedAt(LocalDateTime.now());

        userRepository.save(user);

        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }

    @Override
    @Transactional
    @Caching(evict = {
        @CacheEvict(value = CacheConstant.USER_DETAILS, key = "#result.username"),
        @CacheEvict(value = CacheConstant.USER, key = "#userId")
    })
    public User updateUserPassword(Long userId, PasswordUpdateDTO passwordUpdateDTO) {
        User user = self.findUserById(userId);

        // Verify old password
        if (!passwordEncoder.matches(passwordUpdateDTO.getOldPassword(), user.getPassword())) {
            throw new CommonException(ExceptionConstant.USER_INVALID_CREDENTIALS);
        }

        // Update with new password
        LocalDateTime now = LocalDateTime.now();
        String newEncodedPassword = passwordEncoder.encode(passwordUpdateDTO.getNewPassword());
        user.setPassword(newEncodedPassword);
        userRepository.updatePassword(userId, newEncodedPassword, now);
        return user;
    }

    @Override
    @Cacheable(value = CacheConstant.USER, key = "#userId")
    public User findUserById(Long userId) {
         return userRepository.findById(userId)
                .orElseThrow(() -> new CommonException(ExceptionConstant.USER_NOT_FOUND));
    }

    @Override
    @Cacheable(value = CacheConstant.USER, key = "'n_' + #username") // name prefix to avoid collision with userId
    public User findUserByUsername(String username) {
        return userRepository.findByUsername(username)
                .orElseThrow(() -> new CommonException(ExceptionConstant.USER_NOT_FOUND));
    }
}